package com.tms.ned.controlmonitor.adapters;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JOptionPane;

import com.tms.ned.controlmonitor.ComponentChangeEvent;
import com.tms.ned.controlmonitor.ComponentChangeListener;
import com.tms.ned.controlmonitor.EqualsComparator;
import com.tms.ned.controlmonitor.decorators.ColorComponentDecorator;
import com.tms.ned.controlmonitor.decorators.ComponentDecorator;
import com.tms.ned.controlmonitor.validate.ValidationStatus;
import com.tms.ned.controlmonitor.validate.Validator;

/*
 * Licensed under the Apache License, Version 2.0;<br> 
 * You may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless 
 * required by applicable law or agreed to in writing, software distributed under the License 
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and limitations under the License. 
 */

/**
 * A default base class for a ComponentAdapter implementation, which is used as
 * a base class for easily creating other ComponentAdapters. This class takes a
 * Validator instance (optional) and uses it for implementing the validate()
 * method. This class also allows the use of a ComponentDecorator that decorates
 * the component when its validity status changes.
 *
 * @author bshannon
 *
 */
public abstract class AbstractComponentAdapter implements ComponentAdapter {

	/** The component that is being adapted. * */
	private Component component = null;

	/**
	 * The ComponentChangeListeners that get notified when the component
	 * changes. *
	 */
	private Set<ComponentChangeListener> changeListeners = new HashSet<ComponentChangeListener>();

	/** The name of the component, use for debugging or information purposes. * */
	private String name = "UntitledComponent";

	/**
	 * A ComponentDecorator that will decorate the component whenever it
	 * changes. *
	 */
	private ComponentDecorator decorator = null;

	/** The validator used to determine the valid status of the component. * */
	private Validator validator = null;

	/** The most recent validation status from the last call to validate() * */
	private ValidationStatus currentValidationStatus = ValidationStatus.VALID;

	/**
	 * A Comparator used to compare the initial value with the current value. If
	 * the comparator returns 0, then the component is considered to be
	 * unchanged, ie. not "dirty".
	 */
	private Comparator changeComparator = new EqualsComparator();

	/**
	 * True when the setInitialValue was called.
	 */
	private boolean initialValueSet;

	/**
	 * Constructs an AbstractComponentAdapter
	 *
	 * @param name
	 *            The name of the component. Used for debugging purposes.
	 * @param component
	 *            The actual component that is being watched.
	 * @param validator
	 *            A validator that can determine the validity of the component.
	 */
	public AbstractComponentAdapter(String name, Component component, Validator validator) {
		this.component = component;
		this.name = name;
		this.decorator = new ColorComponentDecorator(Color.red, component.getBackground());
		this.validator = validator;

		component.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.isControlDown()) {
					String msg = "Name: " + getName() + "\ninitialValue: " + getInitialValue() + "\nCurrentValue: "
							+ getCurrentValue() + "\nValid? " + getCurrentValidationStatus().isValid() + "\nDirty? "
							+ isDirty() + "\nClass: " + getComponent().getClass() + "\nAdapter: "
							+ AbstractComponentAdapter.class.getName();
					JOptionPane.showMessageDialog(AbstractComponentAdapter.this.component, msg);
				}
			}
		});
	}

	/**
	 * Adds a change listener to the component. The
	 * {@link ComponentChangeListener} will be notified when the value of the
	 * adapted component changes. Whenever getCurrentValue() would return a
	 * different value than it has before, the listener will be notified.
	 */
	public final void addChangeListener(ComponentChangeListener c) {
		changeListeners.add(c);
	}

	/**
	 * Removes a change listener from the component.
	 */
	public final void removeChangeListener(ComponentChangeListener c) {
		changeListeners.remove(c);
	}

	/**
	 * Returns the UI component that is being monitored.
	 */
	public final Component getComponent() {
		return component;
	}

	/**
	 * Returns the name of the component, useful for debugging or informational
	 * purposes.
	 */
	public final String getName() {
		return name;
	}

	/**
	 * Returns whether or not the component is dirty; ie. whether or not the
	 * initial value matches the current value. The changeComparator is a
	 * Comparator used to determine equality, which by default is implemented
	 * with a comparator that uses the control's value's equals() method.
	 */
	public final boolean isDirty() {
		return changeComparator.compare(getInitialValue(), getCurrentValue()) != 0;
	}

	/**
	 * Shortcut to check the validity of the adapted component. Returns the same
	 * value as getValidationStatus().isValid().
	 */
	public final boolean isValid() {
		return getCurrentValidationStatus().isValid();
	}

	/**
	 * Returns the decorator for the component. This is the class that handles
	 * observing the state of the component and decorates the component
	 * accordingly.
	 */
	public final ComponentDecorator getComponentDecorator() {
		return decorator;
	}

	/**
	 * Sets the ComponentDecorator to use, then automatically calls validate()
	 * to revalidate the component.
	 *
	 * @param decorator
	 *            The ComponentDecorator to use to decorate the component.
	 */
	public final void setComponentDecorator(ComponentDecorator decorator) {
		this.decorator = decorator;
		validate(); // Component hasn't changed, but might need redecoration.
	}

	/**
	 * The comparator used to determine whether or not the component's current
	 * value equals its initial value during a call to "isDirty()".
	 *
	 * @param changeComparator
	 *            a Comparator
	 */
	public final void setChangeComparator(Comparator changeComparator) {
		this.changeComparator = changeComparator;
	}

	/**
	 * Validates the component using its Validator, then decorates the component
	 * with its ComponentDecorator.
	 */
	public final void validate() {
		if (validator != null) {
			currentValidationStatus = validator.validate();
		}
		getComponentDecorator().decorateComponent(this);
	}

	/**
	 * Returns the most recently determined ValidationStatus for the component.
	 */
	public final ValidationStatus getCurrentValidationStatus() {
		if (!initialValueSet) {
			throw new RuntimeException(
					"Component Adapter's setInitialValue() method was never called.  It needs to be called before getCurrentValidationStatus().");
		}
		return currentValidationStatus;
	}

	public final void setInitialValue(Object o) {
		initialValueSet = true;
		changeInitialValue(o);
	}

	/**
	 * Implement this method to change your initial value, as if you were just
	 * going to be registered again.
	 *
	 * @param o
	 */
	protected abstract void changeInitialValue(Object o);

	/**
	 * Call this in your subclass whenever your component changes.
	 *
	 * @param event
	 *            The ComponentChangeEvent describing the component's changes.
	 */
	protected void fireComponentChangeEvent(ComponentChangeEvent event) {
		for (ComponentChangeListener listener : changeListeners) {
			listener.componentChanged(event);
		}
	}

	public final void setValidator(Validator validator) {
		this.validator = validator;
		ComponentChangeEvent event = new ComponentChangeEvent(this, this);
		fireComponentChangeEvent(event);
	}

}
